package com.github.bryx.workflow.command.executor;

import com.github.bryx.workflow.command.SubmitUserTaskCommand;
import com.github.bryx.workflow.domain.*;
import com.github.bryx.workflow.domain.process.runtime.TaskObject;
import com.github.bryx.workflow.domain.process.runtime.TaskObjectAssignee;
import com.github.bryx.workflow.dto.runtime.CreateWorkflowInstanceActivityDto;
import com.github.bryx.workflow.dto.runtime.UpdateWorkflowInstanceDto;
import com.github.bryx.workflow.dto.runtime.UpdateWorkflowTaskInstanceDto;
import com.github.bryx.workflow.dto.runtime.UpdateWorkflowTimerJobDto;
import com.github.bryx.workflow.event.UserTaskEnterEvent;
import com.github.bryx.workflow.event.UserTaskSubmitEvent;
import com.github.bryx.workflow.exception.WorkflowRuntimeException;
import com.github.bryx.workflow.handler.WorkflowInstanceAware;
import com.github.bryx.workflow.service.WorkflowBuildTimeService;
import com.github.bryx.workflow.service.WorkflowRuntimeService;
import com.github.bryx.workflow.service.process.ProcessService;
import com.github.bryx.workflow.util.CollectionsUtil;
import com.github.bryx.workflow.util.WorkflowEngineUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @Author jameswu
 * @Date 2021/6/10
 **/
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class SubmitUserTaskCommandExecutor extends CommandExecutor<List<String>> {

    @Autowired
    WorkflowBuildTimeService workflowBuildTimeService;

    @Autowired
    WorkflowRuntimeService workflowRuntimeService;

    @Autowired
    ProcessService processService;

    WorkflowTaskInstance workflowTaskInstance;

    WorkflowInstance workflowInstance;

    WorkflowDefRev workflowDefRev;

    WorkflowDef workflowDef;

    private void init(){
        workflowTaskInstance = workflowRuntimeService.query().getWorkflowTaskInstanceById(this.getCommand().getWorkflowTaskInstanceId());
        workflowInstance = workflowRuntimeService.query().getWorkflowInstanceById(this.getCommand().getWorkflowInstanceId());
        Validate.notNull(workflowTaskInstance, "task not found");
        workflowDefRev = workflowBuildTimeService.query().getWorkflowDefRevById(workflowInstance.getDefRevId());
        workflowDef = workflowBuildTimeService.query().getWorkflowDefById(workflowDefRev.getDefId());
        workflowDef.setRev(workflowDefRev);
    }

    @Override
    @Transactional
    public List<String> run() {
        this.init();

        SubmitUserTaskCommand command = this.getCommand().as(SubmitUserTaskCommand.class);
        // 更新任务的状态
        workflowRuntimeService.manager().updateWorkflowTaskInstance(UpdateWorkflowTaskInstanceDto.builder()
                .executorId(command.getExecutorId())
                .status(WorkflowTaskInstance.WorkflowTaskInstanceStatus.COMPLETED)
                .id(workflowTaskInstance.getId())
                .build());

        // 更新timer job状态
        workflowRuntimeService.manager().updateWorkflowTimerJob(UpdateWorkflowTimerJobDto.builder()
                .workflowTaskIds(Lists.newArrayList(workflowTaskInstance.getId()))
                .status(WorkflowTimerJob.Status.CLOSED)
                .build());

        // 更新流程实例的表单数据
        workflowRuntimeService.manager().updateWorkflowInstance(UpdateWorkflowInstanceDto.builder()
                .id(command.getWorkflowInstanceId())
                .executorId(command.getExecutorId())
                .formData(command.getFormData())
                .build());

        // 分配用户，提交并指定受理人 ｜ 提交
        final Map<String, WorkflowDefProcessConfig.UserTaskConfig> userTasksConfig = workflowDefRev.getProcessConfig().getUserTasks();
        List<String> newTaskIds = processService.execute(workflowInstance.getProcessId(), workflowTaskInstance.getProcessTaskId(), command.getExecutorId(), command.getFormData(), (tasks, processObject) -> {
            Map<String, List<TaskObjectAssignee>> taskObjectAssigneesMap = Maps.newHashMap();
            tasks.forEach(taskObject -> {
                Optional.ofNullable(userTasksConfig.get(taskObject.getDefinitionId()))
                        .orElseThrow(()->new WorkflowRuntimeException(String.format("please config user task for %s", taskObject.getDefinitionId())))
                        .setTaskDefId(taskObject.getDefinitionId());
                List<TaskObjectAssignee> taskObjectAssignees = Optional.ofNullable(WorkflowEngineUtil.createTaskObjectAssignees(command.getAssigneeUserIds(), command.getAssigneeGroupIds()))
                        .orElse(this.getWorkflowInstanceAware().userTaskAssign(workflowDef.getProcessDefKey(), workflowInstance, workflowTaskInstance, userTasksConfig.get(taskObject.getDefinitionId())));
                taskObjectAssigneesMap.put(taskObject.getId(), Optional.ofNullable(taskObjectAssignees).orElse(Lists.newArrayList()));
            });
            return taskObjectAssigneesMap;
        });

        List<WorkflowTaskInstance> newWorkflowTaskInstances = Lists.newArrayList();
        if (CollectionsUtil.empty(newTaskIds)){
            // 可以检查是不是流程已经结束了
            List<WorkflowTaskInstance> workflowTaskInstances = workflowRuntimeService.query().getWorkflowTaskInstances(workflowInstance.getId());
            if (CollectionsUtil.empty(workflowTaskInstances)){
                workflowRuntimeService.manager().updateWorkflowInstance(UpdateWorkflowInstanceDto.builder()
                        .id(workflowInstance.getId())
                        .status(WorkflowInstance.WorkflowInstanceStatus.COMPLETED)
                        .build());
            }
        }else{
            // 数据库记录任务
            List<TaskObject> tasks = processService.getTasks(newTaskIds);
            newWorkflowTaskInstances = this.getCommandExecutorHelper().createWorkflowTaskInstances(workflowInstance.getId(), workflowDefRev, tasks);
            //
            this.getCommandExecutorHelper().createWorkflowTimerJobsIfNecessary(workflowDef.getProcessDefKey(), workflowInstance, newWorkflowTaskInstances, workflowDefRev.getProcessConfig(), this.getWorkflowInstanceAware());
        }
        // 发送UserTaskEnterEvent
        newWorkflowTaskInstances.forEach(taskInstance->{
            this.getWorkflowInstanceAware().handleEvent(workflowDef.getProcessDefKey(), UserTaskEnterEvent.builder()
                    .previousTaskInstance(workflowTaskInstance)
                    .workflowTaskInstance(taskInstance)
                    .formData(command.getFormData())
                    .build());
        });
        // 发送UserTaskSubmitEvent
        this.getWorkflowInstanceAware().handleEvent(workflowDef.getProcessDefKey(), UserTaskSubmitEvent.builder()
                .workflowTaskInstance(workflowTaskInstance)
                .newWorkflowTaskInstances(newWorkflowTaskInstances)
                .formData(command.getFormData())
                .build());

        // 创建历史
        CreateWorkflowInstanceActivityDto createWorkflowInstanceActivityDto = CreateWorkflowInstanceActivityDto.builder()
                .operatorId(command.getExecutorId())
                .workflowInstanceId(workflowTaskInstance.getWorkflowInstanceId())
                .operateName(WorkflowUserOperationType.SUBMIT.getName())
                .workflowTaskName(workflowTaskInstance.getName())
                .workflowTaskId(workflowTaskInstance.getId())
                .build();
        createWorkflowInstanceActivityDto.extractActivityFormData(workflowTaskInstance.getProcessTaskDefId(), workflowDefRev.getProcessConfig(), command.getFormData());
        this.getCommandExecutorHelper().createActivity(createWorkflowInstanceActivityDto);

        return newWorkflowTaskInstances.stream().map(WorkflowTaskInstance::getId).collect(Collectors.toList());
    }

}
